Vaex 低内存占用 Pandas 替代
背景
对超大型数据集做数据分析的时候,会面临内存装不下的问题,大量精力会被数据拆分、分批处理牵扯。
Pandas 是 Python 下最常用的数据分析框架,由于它将数据全部加载到内存中,因此面临超大数据集会有装不下的问题。
Vaex 是一个 Pandas 替代库,通过采用内存映射、懒加载、引用传值、延迟计算的方式,能够大大节省内存,因此适合于对超大型数据集进行分析。
两大优势:
- 数据加载速度快
- 内存占用低
缺点:
- 普及度小
- 网上资料、教程少
- Vaex 不支持 Pandas 的全部功能
Vaex 还有一个好的特性是跟 Pandas Dataframe 灵活互转,对已有的 Pandas 能实现更好的兼容。
内存节省
Vaex 为什么能节省内存?主要有以下原因。
内存映射
Vaex 以二进制格式存储数据(HDF5、Apache Arror、Apache Parquet),在加载数据的时候,它只是对数据格式进行内存映射,并没有加载内存数据。
因此速度非常快,同时内存开销很小。
懒加载
在 Vaex 中,数据只有在真正需要的时候才会被加载到内存中。
引用传值
在对 DataFrame 进行过滤的时候,并不会复制一个新的 DataFrame,而是在老的 DataFrame 之上对引用做过滤,从而节省大量内存。
延迟计算
在 Vaex 中有虚拟列的概念,即基于已有的列通过计算得到的新列,这些新列中的数据也是延迟计算的,只有在用到的时候,才会进行实际计算。
问题
describe 的性能?
describe(算最大、最小、均值、标准差)的时候,需要对数据进行遍历。
Vaex 能够实现以有限内存,对远大于内存的数据集计算整体信息。
由此可以看出,遍历数据的时候,数据在内存中不是常驻的。
排序是否要加载所有数据?
我觉得,应该遍历一边数据,然后按照排序后的建立索引。
实际效果
到底能省多少内存?
《python vaex处理大数据集》做了一个实验,vaex 内存占用只有 pandas 的 1/10。
文中还提到了一个 chunk 特性,能将大数据集拆分保存,这跟我需要的分片场景是十分像的,我如果能将分片按照 chunk 格式保存,加载的时候既不需要手动 concat 了。
扩展生态
vaex 有一系列扩展生态:
框架 | 说明 |
---|---|
vaex-core | 核心库 |
vaex-hdf5 | hdf5 支持 |
vaex-arrow | Apache Arrow 支持 |
vaex-viz | 基于 Matplotlib 的可视化 |
vaex-jupyter | jupyter 小部件 |
vaex-server | 作为 Server,支持远程访问数据表 |
vaex-distributed | 分布式运算 |
vaex-qt | Qt 支持 |
vaex | meta 包,包含以上库 |
vaex-ml | 机器学习 |
数据导入
NumPy 数据变 DataFrame
import vaex
import numpy as np
x = np.arange(5)
y = x**2
df = vaex.from_arrays(x=x, y=y)
CSV 变 DataFrame
import vaex
for i, df in enumerate(vaex.from_csv('taxi.csv', chunk_size=100_000)):
df = df[df.passenger_count < 6]
df.export_hdf5(f'taxi_{i:02}.hdf5')
这里面的 chunk size 引起了我的注意,好像 hdf5 也支持 chunk。
Pandas DataFrame 变 Vaex's
import vaex, pandas as pd
df_pandas = pd.from_csv('test.csv')
df = vaex.from_pandas(df_pandas)
导入 S3 数据
nyctaxi = vaex.open('s3://vaex/taxi/yellow_taxi_2009_2015_f32.hdf5?anon=true')
nyctaxi.head(5)
其中,数据也是懒加载的,只会下载必要部分,并缓存在本地。
数据导出
DataFrame.to_pandas_df
DataFrame.export_hdf5
DataFrame
Vaex 的核心也是 DataFrame,Vaex 的 DataFrame 比 Pandas 的更加高效。
特性:
- 在 Vaex 的表达式系统中,
df.x
、df['x']
、df.col.x
都是表达式。
- 列和表达式都是懒加载的,如
df.x * np.sin(df.y)
什么也不干,直到实际访问结果 - 虚拟列都是懒加载的,如
df['r'] = df.x/df.y
- 对数据进行筛选:
df.select(df.x < 0)
- 对数据进行过滤:
df_negative = df[df.x < 0]
示例数据集
import vaex
df = vaex.example()
示例数据集,行数大于 30w,有 9 个列
列访问
访问数据集中的一列:
df.x # df.col.x or df['x'] are equivalent
返回值不是一个 Numpy 数组,而是一个 Expression。 通过 .values 将数据加载到内存中,获取到数组:
df.x.values
表达式运算
队列进行表达式运算:
import numpy as np
np.sqrt(df.x**2 + df.y**2 + df.z**2)
得到的还是一个表达式:Expression,并没有实际运算结果
虚拟列(Virtual Columns)
使用表达式 Expression 作为一个列,惰性求值,不用的话不占内存。
df['r'] = np.sqrt(df.x**2 + df.y**2 + df.z**2)
其中:r
就是一个虚拟列。
选择和过滤
条件选择
将条件选择实体化,拆分成选择和计算两个过程:
df.select(df.x < 0)
df.evaluate(df.x, selection=True)
这适合于数据集动态变化的情况,方便基于同一个选择求值。
过滤
跟 Pandas 比较像:
# 条件筛选
df_negative = df[df.x < 0]
# 获取指定列
df_negative[['x', 'y', 'z', 'r']]
统计运算
# 数据个数
df.count()
# 均值
df.mean(df.x)
# 选择部分的均值
df.mean(df.x, selection=True)
频数
统计在 limits 区间内的频数分布,shape 是分多少个格子:
counts_x = df.count(binby=df.x, limits=[-10, 10], shape=64)
使用 matplotlib 绘制图像:
import matplotlib.pylab as plt
plt.plot(np.linspace(-10, 10, 64), counts_x)
plt.show()
二维频数分布:
xycounts = df.count(
binby=[df.x, df.y],
limits=[[-10, 10], [-10, 20]],
shape=(64, 128))
二维绘图:
plt.imshow(xycounts.T, origin='lower', extent=[-10, 10, -10, 20])
plt.show()
网络资源
手把手教你如何用 Python和Vaex 在笔记本上分析 100GB 数据
- 伦敦出租车数据集
- 提到怎么对 NumPy 进行加速的多种手段
Vaex真香!几秒钟就能处理数十亿行数据,比Pandas、Dask更好用
- 给出了一个对期货螺纹钢的实例
- apply 方法跟 Pandas 有点不一样、
- 一些使用的实例
- 总结了 vaex 的常用使用方法
- 格式有点乱,整体还是挺全的